package com.tianji.promotion.service.impl;

import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author root
 * @since 2023-11-23
 */
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    private final ICouponScopeService couponScopeService;//优惠券的限定范围业务类
    private final IExchangeCodeService exchangeCodeService;//兑换码的业务类
    private final IUserCouponService userCouponService;
    private final StringRedisTemplate redisTemplate;

    @Override
    @Transactional
    public void saveCoupon(CouponFormDTO dto) {
        // 1. dto 转 po 保存优惠券 coupon表
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        this.save(coupon);
        // 2. 判断是否限定了范围 dto.specific  如果为false 直接 return
        if(!dto.getSpecific()){
            return;
        }
        List<Long> scopes = dto.getScopes();
        // 3. 如果dto.specific 为 true 需要校验 dto.scopes
        if(CollUtils.isEmpty(scopes)){
            throw new BadRequestException("分类id 不能为空");
        }
        // 4. 保存优惠券的限定范围
//        List<CouponScope> csList = new ArrayList<>();
//        for (Long scope : scopes){
//            CouponScope couponScope = new CouponScope();
//            couponScope.setCouponId(coupon.getId());
//            couponScope.setBizId(scope);
//            couponScope.setType(1);
//            csList.add(couponScope);
//        }
        List<CouponScope> csList = scopes
                .stream()
                .map(aLong -> new CouponScope().setCouponId(coupon.getId()).setBizId(aLong).setType(1))
                .collect(Collectors.toList());
        couponScopeService.saveBatch(csList);
    }

    @Override
    public PageDTO<CouponPageVO> queryCouponPage(CouponQuery query) {
        // 1. 分页条件查询优惠券表 Coupon
        Page<Coupon> page = this.lambdaQuery()
                .eq(query.getType() != null, Coupon::getDiscountType, query.getType())
                .eq(query.getStatus() != null, Coupon::getStatus, query.getStatus())
                .like(StringUtils.isNotBlank(query.getName()), Coupon::getName, query.getName())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)){
            return PageDTO.empty(page);
        }

        // 2. 封装 vo 返回
        List<CouponPageVO> voList = BeanUtils.copyList(records, CouponPageVO.class);

        return PageDTO.of(page,voList);
    }

    @Override
    public void issueCoupon(Long id, CouponIssueFormDTO dto) {
        // 1. 校验
        if(id == null || !id.equals(dto.getId())){
            throw new BadRequestException("非法参数");
        }
        // 2. 校验优惠券 id 是否存在
        Coupon coupon = this.getById(id);
        if(coupon == null){
            throw new BadRequestException("优惠券不存在");
        }
        // 3. 校验优惠券状态  只有待发放和暂停状态才能发放
        if(coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != CouponStatus.PAUSE){
            throw new BizIllegalException("只有待发放和暂停中的优惠券才能发放");
        }
        LocalDateTime now = LocalDateTime.now();

        boolean isBeginIssue = dto.getIssueBeginTime() == null || !dto.getIssueBeginTime().isAfter(now);// 该变量代表优惠券是否立刻发放
        // 4. 修改优惠券的领取 开始和结束日期 使用有效期开始和结束日  天数 状态
        // 方式1:
//        if(isBeginIssue){
//            // 立刻发放
//            coupon.setIssueBeginTime(now);
//            coupon.setIssueEndTime(dto.getIssueEndTime());
//            coupon.setStatus(CouponStatus.ISSUING);//优惠券状态改为发放中
//            coupon.setTermDays(dto.getTermDays());
//            coupon.setTermBeginTime(dto.getTermBeginTime());
//            coupon.setTermEndTime(dto.getTermEndTime());
//        }else {
//            // 定时发放
//            coupon.setIssueBeginTime(dto.getIssueBeginTime());
//            coupon.setIssueEndTime(dto.getIssueEndTime());
//            coupon.setStatus(CouponStatus.UN_ISSUE);//优惠券状态改为发放中
//            coupon.setTermDays(dto.getTermDays());
//            coupon.setTermBeginTime(dto.getTermBeginTime());
//            coupon.setTermEndTime(dto.getTermEndTime());
//        }
//        this.updateById(coupon);

        // 方式2:
        Coupon couponDB = BeanUtils.copyBean(dto, Coupon.class);
        if (isBeginIssue){
            couponDB.setStatus(CouponStatus.ISSUING);
            couponDB.setIssueBeginTime(now);
        }else {
            couponDB.setStatus(CouponStatus.UN_ISSUE);
        }
        this.updateById(couponDB);

        // 3. 如果优惠券是立刻发放，将优惠券信息（优惠券 id,领券开始和结束日期，发行总数量，限领数量）采用Hash存入redis
        if(isBeginIssue){
            String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;//prs:coupon:优惠券id
//            redisTemplate.opsForHash().put(key,"issueBeginTime", String.valueOf(DateUtils.toEpochMilli(now)));
//            redisTemplate.opsForHash().put(key,"issueEndTime", String.valueOf(DateUtils.toEpochMilli(dto.getIssueEndTime())));
//            redisTemplate.opsForHash().put(key,"totalNum",String.valueOf(coupon.getTotalNum()));
//            redisTemplate.opsForHash().put(key,"userLimit",String.valueOf(coupon.getUserLimit()));

            Map<String,String> map = new HashMap<>();
            map.put("issueBeginTime", String.valueOf(DateUtils.toEpochMilli(now)));
            map.put("issueEndTime", String.valueOf(DateUtils.toEpochMilli(dto.getIssueEndTime())));
            map.put("totalNum",String.valueOf(coupon.getTotalNum()));
            map.put("userLimit",String.valueOf(coupon.getUserLimit()));
            redisTemplate.opsForHash().putAll(key,map);
        }
        // 4.  如果优惠券的领取方式是指定发放且优惠券之前的状态是待发放需要生成兑换码
        if(coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT){
            coupon.setIssueEndTime(couponDB.getIssueEndTime());//兑换码的截至时间就是优惠券的兑换期限
            exchangeCodeService.asyncGenerateExchangeCode(coupon);//异步生成兑换码
        }

    }

    //查询正在发放中的优惠券
    @Override
    public List<CouponVO> queryIssuingCoupons() {
        // 1. 查询db  coupon  条件：发放中 手动领取
        List<Coupon> couponList = this.lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if(CollUtils.isEmpty(couponList)){
            return CollUtils.emptyList();
        }
        // 2. 查询用户券表user_coupon   条件 :当前用户  发放中的优惠券id
        Set<Long> couponIds = couponList.stream().map(Coupon::getId).collect(Collectors.toSet());//正在发放中的优惠券id集合
        List<UserCoupon> list = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        // 2.1 统计当前用户 针对每一个券的领取数量
        // 方法1
//        Map<Long,Long> issueMap = new HashMap<>();//代表当前用户针对 键：每一个券 值：领取数量
//        for(UserCoupon userCoupon : list){
//            Long num = issueMap.get(userCoupon.getCouponId());
//            if(num == null){
//                issueMap.put(userCoupon.getCouponId(),1L);
//            }else {//不是第一次查出来，领取数量加一
//                issueMap.put(userCoupon.getCouponId(),num + 1L);
//            }
//
//        }
        //方法2   stream
        Map<Long, Long> issueMap = list.stream()
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));


        // 2.2 统计当前用户 针对每一个券 的已领取且未使用的数量
        Map<Long, Long> unuseMap = list.stream()
                .filter(c->c.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));



        // 3. po 转 vo返回
        List<CouponVO> voList = new ArrayList<>();
        for(Coupon c : couponList){
            CouponVO vo = BeanUtils.copyBean(c, CouponVO.class);

            Long issNum = issueMap.getOrDefault(c.getId(),0L);
            //                    优惠券还有剩余，                        用户拥有小于用户领取上限
            boolean avaliable = c.getIssueNum() < c.getTotalNum() && issNum.intValue() < c.getUserLimit();
            vo.setAvailable(avaliable);//是否可以领取

            // 用户领取还未使用
            boolean received = unuseMap.getOrDefault(c.getId(),0L) > 0;
            vo.setReceived(received);//是否可以使用

            voList.add(vo);
        }
        return voList;
    }
}
